package org.summer.gateway.security;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;

import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.summer.gateway.service.SecurityUserService;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

@Log4j2
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class GatewaySecurityConfig {
    private static final String PASSWORD_ENCODE_ID = "bcrypt";
    /**
     * 网关安全自定义配置
     */
    private final SecurityProperties properties;
    /**
     * 用户服务接口实现类
     */
    private final SecurityUserService userService;
    /**
     * 网关安全上下文件
     */
    private final ServerSecurityContextRepository contextRepository;
    /**
     * 网关权限验证管理器
     */
    private final GatewayAuthorizationManager authorizationManager;
    /**
     * 用户登录成功处理器
     */
    private final LoginSuccessHandler loginSuccessHandler;
    /**
     * 用户登录失败处理器
     */
    private final LoginFailureHandler loginFailureHandler;
    /**
     * 用户登录认证失败处理
     */
    private final AuthenticationEntryPoint authenticationEntryPoint;
    /**
     * 用户登出处理器
     */
    private final LogoutHandler logoutHandler;
    /**
     * 用户退出登录成功处理器
     */
    private final LogoutSuccessHandler logoutSuccessHandler;
    /**
     * 用户访问拒绝处理器
     */
    private final AccessDeniedHandler accessDeniedHandler;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        log.info("用户配置跨域请求地址: {}", properties.getAllowedOrigins());
        log.info("用户配置安全框架忽略地址: {}", Arrays.asList(properties.getIgnoreUrls()));
        http
                .securityContextRepository(contextRepository)
                .authenticationManager(authenticationManager())
                .authorizeExchange(exchange ->
                                exchange
                                        // 静态资源无需认证
                                        .matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                                        .pathMatchers(this.properties.getIgnoreUrls()).permitAll()
                                        .anyExchange()
                                        .access(authorizationManager)
                )
                .formLogin(login -> login
                        .authenticationSuccessHandler(loginSuccessHandler)
                        .authenticationFailureHandler(loginFailureHandler)
                        .requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login"))
                        .authenticationManager(authenticationManager())
                        .authenticationEntryPoint(authenticationEntryPoint)
                        .securityContextRepository(contextRepository)
                )
                .logout(logout -> logout
                        .logoutHandler(logoutHandler)
                        .logoutSuccessHandler(logoutSuccessHandler)
                        .requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/logout"))
                )
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint(authenticationEntryPoint)
                        .accessDeniedHandler(accessDeniedHandler)
                )
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .cors(corsSpec -> corsSpec.configurationSource(configurationSource()));
        return http.build();
    }

    /**
     * 用户认证管理器
     */
    @Bean
    public ReactiveAuthenticationManager authenticationManager() {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(new TokenAuthenticationManager());
        // 必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效
        managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(userService));
        return new DelegatingReactiveAuthenticationManager(managers);
    }

    /**
     * 密码加密器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        Map<String, PasswordEncoder> encoders = new HashMap<String, PasswordEncoder>();
        encoders.put(PASSWORD_ENCODE_ID, new BCryptPasswordEncoder());
        return new DelegatingPasswordEncoder(PASSWORD_ENCODE_ID, encoders);
    }

    /**
     * web配置
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> {
            web.ignoring()
                    .requestMatchers("/login");
        };
    }

    /**
     * 跨域配置
     */
    @Bean
    public CorsConfigurationSource configurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 接收cookie
        config.setAllowedOrigins(this.properties.getAllowedOrigins());
        config.addAllowedHeader("*"); // 允许任何头
        config.addAllowedMethod("*");// 允许任何方法（post、get等）
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}
